﻿// Dependendies:
//  1. JQHelper
//  2. JQuery
//  3. JQuery.Validate
//  4. JSBuilder
//  5. bigfootMVC (bigfootMVC.errorHappened)

using System;
using System.Collections.Generic;
using System.Text;

namespace BigfootMVC.Helpers
{
    

    /// <summary>
    /// This helper class aids you in the creation of a script to execute an AJAX request.
    /// It goes beyond just the execution of the request, it also adds a host of things you can do
    /// once the request excutes successfully, like filling an element with the returned data or 
    /// removing alement from the dom. This class uses the same fluid interface as the other helpers
    /// so you can build on functionality like this .clearElement("elementid").updatePanel("elementid").focus("elementid")
    /// </summary>
    public class AjaxRequest
    {

        /// <summary>
        /// Enumeration used when using jquery with ajax
        /// </summary>
        public enum DataTypeEnum
        {
            html,
            json,
            xml,
            script,
            jsonp,
            text
        }

        /// <summary>
        /// List of methods used on forms and jquery ajax calls
        /// </summary>
        public enum MethodEnum
        {
            @get,
            post
        }

        /// <summary>
        /// Factory method to creat an ajax request that will act against the supplied url
        /// </summary>
        /// <param name="url">Server url the ajax call uses</param>
        public static AjaxRequest Create(string url)
        {
            return Create(url, null);
        }

        /// <summary>
        /// Factory method to creat an ajax request that will act against the supplied url. It uses the String.Format method
        /// with the url to aid in the parametization of the url
        /// </summary>
        /// <param name="url">Server url the ajax call uses. String.Format will be called on this string</param>
        /// <param name="params">The values to merge with the url</param>
        /// <returns>A new AjaxRequest object</returns>
        public static AjaxRequest Create(string url, params string[] @params)
        {
            var obj = new AjaxRequest
            {
                _url = @params != null ? string.Format(url, @params) : url
            };
            return obj;
        }

        // These are the corresponding private methods of the class
        private string _url;
        private string _updatepanel;
        private string _removeElement;
        private string _confirmMessage;
        //private string _successMessage;
        private string _callback;
        private bool _validate;
        private JSObject _postData = new JSObject();
        private MethodEnum _method = MethodEnum.@get;
        private DataTypeEnum _dataType = DataTypeEnum.html;
        private string _redirectToUrl;
        private string _clearElement;
        private string _clearInputs;
        private string _loadUrl;
        private string _loadElement;
        private string _hideElement;
        private string _showElement;
        private string _removeClassElement;
        private string _removeClassNames;
        private string _focus;
        private string _partialForm;
        private bool _clearForm;
        private List<string> _validateInputs = new List<string>();

        private string _clearElementValue;
        private string _formName;


        /// <summary>
        /// Sumits the inputs of the partial form specified
        /// </summary>
        /// <param name="containerId">The partial form element id</param>
        public AjaxRequest PartialForm(string containerId)
        {
            _partialForm = JQHelper.GetElementID(containerId);
            return this;
        }

        /// <summary>
        /// Sets the innerHtml value of the element specified with the data returned from the ajax request
        /// </summary>
        /// <param name="name">Element id that will hold the contents</param>
        public AjaxRequest UpdatePanel(string name)
        {
            _updatepanel = JQHelper.GetElementID(name);
            return this;
        }
        
        /// <summary>
        /// Removes this element once the ajax request completes
        /// </summary>
        /// <param name="name">Element id</param>
        public AjaxRequest RemoveElement(string name)
        {
            _removeElement = JQHelper.GetElementID(name);
            return this;
        }

        /// <summary>
        /// Redirects the user to the url specified once the ajax request completes
        /// </summary>
        /// <param name="url">Url to redirect the user to client side</param>
        public AjaxRequest RedirectToUrl(string url)
        {
            _redirectToUrl = url;
            return this;
        }

        /// <summary>
        /// Sets the focus to the element specified once the ajax request completes
        /// </summary>
        /// <param name="elementId">Element id</param>
        public AjaxRequest focus(string elementId)
        {
            _focus = JQHelper.GetElementID(elementId);
            return this;
        }

        /// <summary>
        /// Adds a confirmation message before the ajax request happens.
        /// </summary>
        /// <param name="message">Text to display to the user in the confirmaiton message</param>
        public AjaxRequest ConfirmMessage(string message)
        {
            _confirmMessage = message;
            return this;
        }
        
        /// <summary>
        /// Calls the supplied function or executes the supplied script once the ajax request completes
        /// </summary>
        /// <param name="function">This could be any valid javscript, either ExecuteOnSuccess() or some actual script like function(data){ alert(data); } </param>
        public AjaxRequest Callback(string function)
        {
            if (!function.Trim().EndsWith(")"))
                function += "()";
            _callback = function;
            return this;
        }

        /// <summary>
        /// Executes the supplied javascript once the ajax request completes. 
        /// It will be wrapped like this: function(responseText, statusText, xhr, $form){ [your code] }
        /// </summary>
        /// <param name="javascript">This could be any valid javscript</param>
        public AjaxRequest CallbackJavascript(string javascript)
        {
            if (!javascript.Trim().EndsWith(";")) javascript += ";";
            _callback = "(function(responseText, statusText, xhr, $form) { " + javascript + "})()";
            return this;
        }

        /// <summary>
        /// Shows the alert on success
        /// </summary>
        /// <param name="msg">The message to display in the alert box</param>
        public AjaxRequest SuccessMessage(string msg)
        {
            return CallbackJavascript("alert('" + msg + "')");
        }

        /// <summary>
        /// Posts the specified JSObject literal values
        /// </summary>
        /// <param name="obj">The JSObject to post</param>
        public AjaxRequest PostData(JSObject obj)
        {
            _postData.Merge(obj);
            return this;
        }

        /// <summary>
        /// Validates the request vefore sumission
        /// </summary>
        public AjaxRequest Validate()
        {
            _validate = true;
            return this;
        }

        /// <summary>
        /// Adds some literal data to the post data
        /// </summary>
        /// <param name="param">The parameter name for the data</param>
        /// <param name="expression">The expression to assign to the param to be posted. Does not add string quotes.</param>
        public AjaxRequest AddData(string param, string expression)
        {
            _postData.Add(param, expression );
            return this;
        }

        /// <summary>
        /// Adds some literal data to the post data
        /// </summary>
        /// <param name="param">The parameter name for the data</param>
        /// <param name="expression">The expression to assign to the param to be posted. Does not add string quotes.</param>
        public AjaxRequest AddData(string param, int expression)
        {
            _postData.Add(param, expression.ToString());
            return this;
        }

        /// <summary>
        /// Adds some literal data to the post data. While also properly escaping and adding quotes around the string value
        /// </summary>
        /// <param name="param">The parameter name for the data</param>
        /// <param name="value">The value to post. This value will be properly escaped and quoted to be included in the script</param>
        public AjaxRequest AddDataString(string param, string value)
        {
            _postData.AddString(param, value);
            return this;
        }

        /// <summary>
        /// Adds the value of an input field to the post data
        /// </summary>
        /// <param name="inputIds">The ID of the input fields to add</param>
        public AjaxRequest AddDataInput(params string[] inputIds)
        {
            foreach (var inputId in inputIds)
            {
                AddData(inputId, JQID(inputId).val().ToString());
                _validateInputs.Add(inputId);
            }
            return this;
        }

        /// <summary>
        /// Determins the method of the request, post / get
        /// </summary>
        public AjaxRequest Method(MethodEnum ajaxMethod)
        {
            _method = ajaxMethod;
            return this;
        }

        /// <summary>
        /// Determines the datatype to be expected as the ajax response
        /// </summary>
        public AjaxRequest DataType(DataTypeEnum type)
        {
            _dataType = type;
            return this;
        }

        /// <summary>
        /// Signals that the request will be doing using the GET http method
        /// </summary>
        public AjaxRequest Get()
        {
            return Method(MethodEnum.@get);
        }

        /// <summary>
        /// Signals that the request will be doing using the POST http method
        /// </summary>
        public AjaxRequest Post()
        {
            return Method(MethodEnum.post);
        }

        /// <summary>
        /// Will do a POST request with the specified form
        /// </summary>
        public AjaxRequest Post(string formId)
        {
            Method(MethodEnum.post);
            _formName = JQHelper.GetElementID(formId);
            return this;
        }

        /// <summary>
        /// Removes the content of the specified element once the ajax request completes
        /// </summary>
        public AjaxRequest ClearElement(string elementId)
        {
            _clearElement = JQHelper.GetElementID(elementId);
            return this;
        }

        /// <summary>
        /// Removes the value of a specific input element
        /// </summary>
        public AjaxRequest ClearElementValue(string elementId)
        {
            _clearElementValue = JQHelper.GetElementID(elementId);
            return this;
        }

        /// <summary>
        /// Will clear the value of the specified inputs once the ajax request completes. You can specify multiple inputs separated by a comma
        /// </summary>
        /// <param name="inputIds">The imputs to clear separated by a comma. i.e. name,lastname,city,state do not include the # seclector in front</param>
        public AjaxRequest ClearInput(string inputIds)
        {
            _clearInputs = inputIds;
            return this;
        }

        /// <summary>
        /// Once the ajax request completes. It will execute another ajax request to load the contents of the url into the element
        /// </summary>
        /// <param name="url">Element to request the contents to be loaded</param>
        /// <param name="elementid">Element id to load with the contents</param>
        public AjaxRequest Load(string url, string elementid)
        {
            _loadUrl = url;
            _loadElement = JQHelper.GetElementID(elementid);
            return this;
        }

        /// <summary>
        /// Hides the element on ajax complete
        /// </summary>
        public AjaxRequest HideElement(string ElementID)
        {
            _hideElement = JQHelper.GetElementID(ElementID);
            return this;
        }

        /// <summary>
        /// Shows the elmenet on ajax complete
        /// </summary>
        public AjaxRequest ShowElement(string ElementID)
        {
            _showElement = JQHelper.GetElementID(ElementID);
            return this;
        }

        /// <summary>
        /// Hides one element and show another on ajax complete
        /// </summary>
        public AjaxRequest HideAndShowElement(string HideElementID, string ShowElementID)
        {
            _hideElement = JQHelper.GetElementID(HideElementID);
            _showElement = JQHelper.GetElementID(ShowElementID);
            return this;
        }

        /// <summary>
        /// Removes a class from an elmenet on ajax complete
        /// </summary>
        public AjaxRequest RemoveClass(string ElementID, string ClassNames)
        {
            _removeClassElement = JQHelper.GetElementID(ElementID);
            _removeClassNames = ClassNames;
            return this;
        }

        /// <summary>
        /// Clears the content of the form on ajax complete
        /// </summary>
        public AjaxRequest ClearForm()
        {
            _clearForm = true;
            return this;
        }
        
        /// <summary>
        /// Creates the callback function to execute once the ajax request completes with all the commands added to the request
        /// </summary>
        private JSBuilder ToCallbackFunction()
        {
            var callback = JSBuilder.Create();
            // Update Panel
            if (string.IsNullOrEmpty(_updatepanel) == false)
            {
                callback.AddLine(JQID(_updatepanel).html("data").ToString());
            }
            // Remove Element
            if (string.IsNullOrEmpty(_removeElement) == false)
            {
                callback.AddLine(JQID(_removeElement).remove().ToString());
            }
            // Hide Element
            if (string.IsNullOrEmpty(_hideElement) == false)
            {
                callback.AddLine(JQID(_hideElement).hide().ToString());
            }
            // Show Element
            if (string.IsNullOrEmpty(_showElement) == false)
            {
                callback.AddLine(JQID(_showElement).show().ToString());
            }
            // Remove classes
            if (string.IsNullOrEmpty(_removeClassElement) == false && string.IsNullOrEmpty(_removeClassNames) == false)
            {
                callback.AddLine(JQID(_removeClassElement).removeClass(_removeClassNames).ToString());
            }
            // Callback function
            if (string.IsNullOrEmpty(_callback) == false)
            {
                callback.AddLine(_callback);
            }
            // Clear the form
            if (_clearForm)
            {
                var selector = "form";
                if (!string.IsNullOrEmpty(_partialForm)) selector = _partialForm;
                if (!string.IsNullOrEmpty(_formName)) selector = _formName;
                
                callback.AddLine(JQ().SelectFields(selector).clearFields().ToString());
                callback.AddLine(JQ(selector + " .error:not(label.error)").removeClass("error").ToString());
                callback.AddLine(JQ(selector + " label.error").remove().ToString());
            }
            // Clear and hide an element
            if (string.IsNullOrEmpty(_clearElement) == false)
            {
                callback.AddLine(JQID(_clearElement).clear().ToString());
            }
            // Clear element value
            if (!string.IsNullOrEmpty(_clearElementValue))
            {
                callback.AddLine(JQ(_clearElementValue).clearVal().ToString());
            }
            // Clear inputs
            if (string.IsNullOrEmpty(_clearInputs) == false)
            {
                foreach (var i in _clearInputs.Split(','))
                {
                    if (i.Trim().Length == 0) continue;
                    callback.AddLine(JQID(i.Trim()).clearVal().ToString());
                }
            }
            // Set the focus
            if (string.IsNullOrEmpty(_focus) == false)
            {
                callback.AddLine(JQID(_focus).focus().ToString());
            }
            // Redirect to url
            if (string.IsNullOrEmpty(_redirectToUrl) == false)
            {
                callback.AddLine(" if (bigfootMVC.errorHappened == false) window.location = " + JSBuilder.GetString(_redirectToUrl));
            }
            // Load data into element
            if (string.IsNullOrEmpty(_loadUrl) == false && string.IsNullOrEmpty(_loadElement) == false)
            {
                callback.AddLine(JQID(_loadElement).load(_loadUrl).show().ToString());
            }

            return callback.Wrap(" function(data) {{ {0} }} ");
        }

        /// <summary>
        /// Creates the script to be outputed to the page
        /// </summary>
        public override string ToString()
        {
            var script = JSBuilder.Create();

            // Set the data to be posted to be an empty JSON object
            var data = "{}";

            // Add any data that has been selected to post
            if (!_postData.IsEmpty())
            {
                data = _postData.ToString();
            }

            // When posting a partial form then set the data to the serialized fields
            if (!string.IsNullOrEmpty(_partialForm))
            {
                data = JQ().SelectFields(_partialForm).fieldSerialize().ToString();
            }

            // When posting a form then serialize it
            if (!string.IsNullOrEmpty(_formName))
            {
                data = JQID(_formName).formToArray().ToString();
            }
            
            // Crete the AJAX request
            script.AddLine("jQuery.{0}({1}, {2}, {3}, {4});", 
                                    _method.ToString(), 
                                    JSBuilder.GetString(_url), 
                                    data, 
                                    ToCallbackFunction().ToString(), 
                                    JSBuilder.GetString(_dataType.ToString())
                          );

            
            // Wrap the ajax call with confirm
            if (string.IsNullOrEmpty(_confirmMessage) == false)
            {
                script.WrapConfirm(_confirmMessage);
            }

            // VALIDATION
            if (_validate)
            {
                // Validate inputs if any 
                if (_validateInputs.Count > 0)
                    script.WrapInputsValidation(_validateInputs.ToArray());
                // Validate a partial form if any
                if (!string.IsNullOrEmpty(_partialForm))
                    script.WrapGroupValidation(_partialForm);
                // Validate the form to be posed
                if (!string.IsNullOrEmpty(_formName))
                    script.WrapFormValidation(_formName);
                // Run a full form validation only when not partial form or validating only specific inputs
                else if (_validateInputs.Count == 0 && string.IsNullOrEmpty(_partialForm))
                    script.WrapParentFormValidation();
            }

            // Properly terminate the statement whith a ;
            script.EndLine();

            // Return a false statement
            script.AddLine("return false");

            // Return the script
            return script.ToString();
        }
        
        /// <summary>
        /// Static function to create JQHelper build simple jQuery commands. Used throughout the JSHelper class.
        /// </summary>
        public static JQHelper JQ()
        {
            return JQHelper.Create();
        }

        /// <summary>
        /// Creates a jQuery helper with the specified selector
        /// </summary>
        public static JQHelper JQ(string selector)
        {
            return JQHelper.Create().Select(selector);
        }

        /// <summary>
        /// Creates a jQuery helper with the specified element id selector
        /// </summary>
        public static JQHelper JQID(string elementId)
        {
            var id = elementId.StartsWith("#") ? elementId : "#" + elementId;
            return JQ(id);
        }

    }
    
}
